package hamaster.jpbc.http;

import static hamaster.jpbc.crypt.aes.Rijndael.AES_BLOCK_SIZE;
import static hamaster.jpbc.pbc.PBC.PBC_G_LENGTH;
import static hamaster.jpbc.pbc.PBC.PBC_ZR_LENGTH;
import hamaster.jpbc.crypt.aes.AES;
import hamaster.jpbc.crypt.aes.OperationMode;
import hamaster.jpbc.pbc.CipherText;
import hamaster.jpbc.pbc.PBC;
import hamaster.jpbc.pbc.PBCDriver;
import hamaster.jpbc.pbc.PlainText;
import hamaster.jpbc.pbc.PublicParameter;
import it.unisa.dia.gas.jpbc.Element;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Properties;

/**
 * pkey=pkey_pbc&
 * request=request_aes_cbc_256
 * @author <a href="mailto:wangyeee@gmail.com">Wang Ye</a>
 */
public class IBCConnection {

	private Element serverID;
	private Element sessionKey;
	private byte[] sessionKeyRaw;
	private PublicParameter publicParameter;
	private URL url;
	private Properties request;
	private HTTPResponseHandler responseHandler;

	public IBCConnection() {
	}

	public void connect() throws IOException {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		request.store(buffer, null);
		byte[] data = buffer.toByteArray();
		int paddingSize;
		if (data.length % AES_BLOCK_SIZE == 0)
			paddingSize = data.length;
		else
			paddingSize = data.length - (data.length % AES_BLOCK_SIZE) + AES_BLOCK_SIZE;
		byte[] aesData = new byte[paddingSize];
		Arrays.fill(aesData, (byte) 0);
		System.arraycopy(data, 0, aesData, 0, data.length);
		byte[] iv = new byte[AES_BLOCK_SIZE];
		byte[] key = new byte[32];
		System.arraycopy(sessionKeyRaw, 0, iv, 0, AES_BLOCK_SIZE);
		System.arraycopy(sessionKeyRaw, AES_BLOCK_SIZE, key, 0, sessionKeyRaw.length - AES_BLOCK_SIZE);;
		AES aes = new AES();
		aes.setBlockSize(paddingSize);
		aes.setKey(key);
		aes.setInitializationVector(iv);
		aes.setOperationMode(OperationMode.CBC);
		// 加密的请求主体 加密方式AES_256_CBC
		byte[] mainCipher = aes.encrypt(aesData);
		PlainText plain = new PlainText();
		plain.setElement(sessionKey);
		plain.setLength(PBC_G_LENGTH);
		CipherText cipher = PBC.instance().encrypt(plain, serverID, publicParameter);
		StringBuffer sb = new StringBuffer("u=");
		sb.append(PBCDriver.hex(cipher.getU().toBytes()));
		sb.append("&v=");
		sb.append(PBCDriver.hex(cipher.getV().toBytes()));
		sb.append("&w=");
		sb.append(PBCDriver.hex(cipher.getW().toBytes()));
		sb.append("&req=");
		sb.append(PBCDriver.hex(mainCipher));
		sb.append("&l=");
		sb.append(data.length);
		sb.append("&action=client");
		HTTPEngine engine = HTTPEngine.instance();
		engine.post(url, null, sb.toString(), responseHandler);
	}

	public void setServerID(byte[] serverIDRaw) {
		if (serverIDRaw.length > PBC_ZR_LENGTH)
			throw new RuntimeException("Too long for a server ID!");
		this.serverID = PBC.instance().getPairing().getZr().newElement();
		byte[] buffer = new byte[PBC_ZR_LENGTH];
		Arrays.fill(buffer, (byte) 0);
		System.arraycopy(serverIDRaw, 0, buffer, PBC_ZR_LENGTH - serverIDRaw.length, serverIDRaw.length);
		serverID.setFromBytes(buffer);
	}

	public Element getServerID() {
		return serverID;
	}

	public void setUrl(String url) throws MalformedURLException {
		this.url = new URL(url);
	}

	public void setUrl(URL url){
		this.url = url;
	}

	public URL getUrl() {
		return url;
	}

	/**
	 * 设置会话主密钥 由初始向量和密钥两部分组成 共48字节
	 * 其中向量16自己密钥32字节
	 * @param sessionKeyRaw 要设置的主密钥
	 */
	public void setSessionKey(byte[] sessionKeyRaw) {
		if (sessionKeyRaw.length > PBC_G_LENGTH)
			throw new RuntimeException("Too long for a session key!");
		this.sessionKeyRaw = sessionKeyRaw;
		this.sessionKey = PBC.instance().getPairing().getGT().newElement();
		byte[] buffer = new byte[PBC_G_LENGTH];
		Arrays.fill(buffer, (byte) 0);
		System.arraycopy(sessionKeyRaw, 0, buffer, PBC_G_LENGTH - sessionKeyRaw.length, sessionKeyRaw.length);
		this.sessionKey.setFromBytes(buffer);
	}

	public Element getSessionKey() {
		return sessionKey;
	}

	public void setPublicParameter(PublicParameter publicParameter) {
		this.publicParameter = publicParameter;
	}

	public PublicParameter getPublicParameter() {
		return publicParameter;
	}

	public void setRequest(Properties request) {
		this.request = request;
	}

	public Properties getRequest() {
		return request;
	}

	public void setResponseHandler(HTTPResponseHandler responseHandler) {
		this.responseHandler = responseHandler;
	}

	public HTTPResponseHandler getResponseHandler() {
		return responseHandler;
	}

//	public static void main(String[] args) {
//		CurveParams params = new CurveParams();
//		try {
//			params.load(new FileInputStream(new File("a.param")));
//			PBC.instance(params).getPairing().getZr().newRandomElement();
//		} catch (Throwable t) {
//			t.printStackTrace();
//			return;
//		}
//		PublicParameter publicParameter;
//		Properties properties = new Properties();
//		try {
//			properties.load(new FileInputStream(new File("config.properties")));
//			publicParameter = new PublicParameter(properties, null);
//		} catch (Exception e) {
//			e.printStackTrace();
//			return;
//		}
//
//		Properties dataToPost = new Properties();
//		dataToPost.setProperty("a", "12abc");
//		dataToPost.setProperty("b", "asf");
//		dataToPost.setProperty("c", "System.arraycopy(sessionKeyRaw, AES_BLOCK_SIZE, key, 0, 32);");
//		dataToPost.setProperty("ds", "33SymmetsetPropertyricKeyGenerator2fd");
//
//		byte[] sessionKeyRaw = SymmetricKeyGenerator.nextKey(48);
//		byte[] iv = new byte[AES_BLOCK_SIZE];
//		byte[] key = new byte[32];
//		System.arraycopy(sessionKeyRaw, 0, iv, 0, AES_BLOCK_SIZE);
//		System.arraycopy(sessionKeyRaw, AES_BLOCK_SIZE, key, 0, 32);
//		IBCConnection conn = new IBCConnection();
//		conn.setPublicParameter(publicParameter);
//		conn.setRequest(dataToPost);
//		conn.setServerID(Hash.sha1("wangyeee@gmail.com"));
//		conn.setSessionKey(sessionKeyRaw);
//		try {
//			conn.setUrl("http://localhost:8080/ibcserver/cs.do");
//		} catch (MalformedURLException e) {
//			e.printStackTrace();
//		}
//		DecryptionHandler dh = new DecryptionHandler(iv, key);
//		conn.setResponseHandler(dh);
//		try {
//			conn.connect();
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
//		for (;;) {
//			if (dh.hasHandled()) {
//				System.out.println(dh.getResponse());
//				break;
//			}
//			try {
//				Thread.sleep(10);
//			} catch (InterruptedException e) {}
//		}
//	}
}
